#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2023/12/7 15:23
# @Author : 杨凯锐

import json
import re
import ssl
import time
from common.ua import header_default
from urllib import request, parse

ssl._create_default_https_context = ssl._create_unverified_context


class Url:
    def __init__(self, url, method='GET', header=header_default['Chrome'], data={}, cookie=None, proxy=None,
                 retry_num=3, time_out=30):
        """
        请求URL
        :param url: 请求地址(必须包含'http://'或'https://')
        :param method: 请求方式(GET/POST, 默认GET)
        :param header: 请求头(dict类型)
        :param data: 请求参数(dict类型, 仅限POST方式, GET方式不可传入data)
        :param cookie: CookieJar对象
        :param proxy: 代理ip(严格遵守格式:xxx.xxx.xxx.xxx:xx)
        :param retry_num: 重试次数(默认3次)
        :param time_out: 超时时间(单位:秒, 默认30秒)
        """
        # 验证url
        if not isinstance(url, str):
            raise ValueError('url must be a string!')
        elif not ('http://' in url or 'https://' in url):
            raise ValueError("url must contain 'http://' or 'https://'!")
        # 验证请求方法
        if not isinstance(method, str):
            raise ValueError('method must be a string!')
        if not ('GET' == method.upper() or 'POST' == method.upper()):
            raise ValueError("method must be a 'GET' or 'POST!'")
        # 验证请求头
        if not isinstance(header, dict):
            raise ValueError('header must be a dict!')
        # 验证请求参数
        if not isinstance(data, dict):
            raise ValueError('data must be a dict!')
        if 'GET' == method.upper() and data:
            raise ValueError('method is to GET not setting data!')
        # 验证代理ip
        if not (proxy is None):
            if not isinstance(proxy, str):
                raise ValueError('proxy must be a string!')
            if "" == proxy:
                raise ValueError('proxy can`t be None!')
            proxy = proxy.replace('localhost', '127.0.0.1')
            if not self.__check_ip(proxy):
                raise ValueError("proxy must be a 'xxx.xxx.xxx.xxx:xx'")
        # 验证重试次数
        if not isinstance(retry_num, int):
            raise ValueError('retry_num must be a int!')
        # 验证超时时间
        if not isinstance(time_out, int):
            raise ValueError('time_out must be a int!')

        self.__url = url
        self.__method = method
        self.__header = header
        self.__data = data
        self.__cookie = cookie
        self.__proxy = {'http': proxy} if proxy is not None else None
        self.__retry_num = retry_num
        self.__time_out = time_out

    @property
    def get_url(self):
        return self.__url

    @property
    def get_method(self):
        return self.__method

    def set_method(self, method):
        """
        请求方式(GET/POST, 默认GET)
        :param method:
        """
        if not isinstance(method, str):
            raise ValueError('method must be a string!')

        self.__method = str(method).upper()

        if not ('GET' == self.__method or 'POST' == self.__method):
            raise ValueError("method must be a 'GET' or 'POST'!")

    @property
    def get_header(self):
        return self.__header

    def set_header(self, header):
        """
        请求头(dict类型)
        :param header:
        """
        if not isinstance(header, dict):
            raise ValueError('header must be a dict!')
        self.__header = header

    @property
    def get_data(self):
        return self.__data

    def set_data(self, data):
        """
        请求参数(dict类型, 仅限POST方式, GET方式不可传入data)
        :param data:
        """
        if not isinstance(data, dict):
            raise ValueError('data must be a dict!')
        if 'GET' == self.__method:
            raise ValueError('method is to GET not setting data!')
        self.__data = data

    @property
    def get_cookie(self):
        return self.__cookie

    def set_cookie(self, cookie):
        """
        CookieJar对象
        :param cookie:
        :return:
        """
        self.__cookie = cookie

    @property
    def get_proxy(self):
        return self.__proxy

    def set_proxy(self, proxy):
        """
        代理ip(严格遵守格式:xxx.xxx.xxx.xxx:xx)
        :param proxy:
        :return:
        """
        if not isinstance(proxy, str):
            raise ValueError('proxy must be a string!')
        if "" == proxy:
            raise ValueError('proxy can`t be None!')
        proxy = proxy.replace('localhost', '127.0.0.1')
        if not self.__check_ip(proxy):
            raise ValueError("proxy must be a 'xxx.xxx.xxx.xxx:xx'")
        self.__proxy = {'http': proxy}

    @property
    def get_retry_num(self):
        return self.__retry_num

    def set_retry_num(self, retry_num):
        """
        重试次数(默认3次)
        :param retry_num:
        :return:
        """
        if not isinstance(retry_num, int):
            raise ValueError('retry_num must be a int!')
        self.__retry_num = retry_num

    @property
    def get_time_out(self):
        return self.__time_out

    def set_time_out(self, time_out):
        """
        超时时间(单位:秒, 默认30秒)
        :param time_out:
        """
        if not isinstance(time_out, int):
            raise ValueError('time_out must be a int!')
        self.__time_out = time_out

    @staticmethod
    def __check_ip(ip):
        """
        校验ip与端口格式
        :param ip:
        :return:
        """
        pattern = '^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|' \
                  '[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|' \
                  '[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])' \
                  ':(([0-9][0-9]{0,3})|([1-5]\d{4})|(6[1-4]\d{3})|(65[1-4]{2})|(655[1-2]\d)|(6553[1-5]))$'
        return True if re.match(pattern, ip) else False

    def __str__(self):
        attr = ''
        for key, value in self.__dict__.items():
            attr += key + ' : ' + str(value) + '\n'
        return '++++++++++++++++++++++++++++++++  CLASS-INFO  ++++++++++++++++++++++++++++++++\n' + 'class_name:\n%sattr:\n%s' % (
        self.__class__.__name__ + '\n', attr)


class Response:

    def __init__(self, response_code, response_content='', cookie=None, request_url=''):
        """
        响应
        :param response_code: 响应状态码
        :param response_content: 响应内容
        :param cookie: 响应后的cookie
        :param request_url: 请求url
        """
        self.__response_code = response_code
        self.__response_content = response_content
        self.__cookie = cookie
        self.__request_url = request_url

    @property
    def get_response_code(self):
        return self.__response_code

    @property
    def get_response_content(self):
        return self.__response_content

    @property
    def get_response_content2json(self, encode='utf-8'):
        return json.loads(str(self.__response_content, encode))

    @property
    def get_response_content2string(self, encode='utf-8'):
        return str(self.__response_content, encode)

    @property
    def get_cookie(self):
        return self.__cookie

    @property
    def get_cookie2dict(self):
        result = []
        for cookie in self.__cookie:
            domain = ''
            path = '/'
            expires = int(time.time()) + 9999
            if not (cookie.domain is None):
                domain = cookie.domain
                if -1 == domain.find('.', 0, 1):
                    domain = '.' + domain
            if not (cookie.path is None) and not (cookie.path is ''):
                path = cookie.path
            if not (cookie.expires is None):
                expires = cookie.expires
            result.append(
                {'name': cookie.name, 'value': cookie.value, 'domain': domain, 'path': path, 'expiry': expires})
        return result

    @property
    def get_request_url(self):
        return self.__request_url

    def __str__(self):
        attr = ''
        for key, value in self.__dict__.items():
            attr += key + ' : ' + str(value) + '\n\t'
        return '++++++++++++++++++++++++++++++++  CLASS-INFO  ++++++++++++++++++++++++++++++++\n' + 'class_name:\n\t%sattr:\n\t%s' % (
        self.__class__.__name__ + '\n', attr)


def request_url(url, encoding='utf-8'):
    """
    HttpRequest
    :param url: Url类对象
    :param encoding: data编码,默认'utf-8'
    :return: Response
    """
    if not url:
        raise ValueError('url can`t be None!')
    response = None
    for i in range(0, url.get_retry_num):
        try:
            if url.get_cookie is not None and url.get_proxy is not None:  # 同时使用cookie与代理
                cookie_processor = request.HTTPCookieProcessor(url.get_cookie)
                proxy_handler = request.ProxyHandler(url.get_proxy)
                opener_r = request.build_opener(cookie_processor, proxy_handler)
            elif not (url.get_cookie is None):  # 使用cookie
                cookie_processor = request.HTTPCookieProcessor(url.get_cookie)
                opener_r = request.build_opener(cookie_processor)
            elif not (url.get_proxy is None):  # 使用代理
                proxy_handler = request.ProxyHandler(url.get_proxy)
                opener_r = request.build_opener(proxy_handler)
            else:
                opener_r = request.build_opener()

            req = request.Request(url=url.get_url, data=parse.urlencode(url.get_data).encode(encoding),
                                  headers=url.get_header, method=url.get_method)
            rsp = opener_r.open(fullurl=req, timeout=url.get_time_out)
            response = Response(response_code=rsp.code, response_content=rsp.read(), cookie=url.get_cookie,
                                request_url=url.get_url)
            opener_r.close()
            break
        except Exception as e:
            if hasattr(e, 'reason'):
                print('Reason: %s' % e.reason)
            elif hasattr(e, 'code'):
                print('Error code: %s' % e.code)
    return response
